/*- * Copyright © 2009 Diamond Light Source Ltd. * * This file is part of GDA. * * GDA is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License version 3 as published by the Free * Software Foundation. * * GDA is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along * with GDA. If not, see <http://www.gnu.org/licenses/>. */ package uk.ac.gda.util.beans.xml; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URI; import java.net.URL; import java.util.HashMap; import java.util.Map; import org.exolab.castor.mapping.Mapping; import org.exolab.castor.mapping.MappingException; import org.exolab.castor.xml.Marshaller; import org.exolab.castor.xml.Unmarshaller; import org.exolab.castor.xml.XMLContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.InputSource; import uk.ac.gda.util.beans.BeansFactory; public class XMLHelpers { /** * Will get a bean of the appropriate type from any an exafs bean file. One of ScanParameters, XanesScanParameters, * SampleParameters, DetectorParameters, OutputParameters is returned. * * @param beanFile * @return the bean * @throws Exception */ public static XMLRichBean getBean(final File beanFile) throws Exception { for (int i = 0; i < BeansFactory.getClasses().length; i++) { if (BeansFactory.isBean(beanFile, BeansFactory.getClasses()[i])) { return (XMLRichBean) XMLHelpers.readBean(beanFile, BeansFactory.getClasses()[i]); } } return null; } /** * Save a bean to a file. * * @param templatePath * @param editingBean * @throws Exception */ public static void saveBean(File templatePath, Object editingBean) throws Exception { URL mapping = null; final Field[] fa = editingBean.getClass().getFields(); for (int i = 0; i < fa.length; i++) { if (fa[i].getName().equalsIgnoreCase("mappingurl")) { mapping = (URL) fa[i].get(null); break; } } writeToXML(mapping, editingBean, templatePath); } /** * Retrieves the bean given the file name, or the bean itself. * <p> * For use in command-line tools where the user can supply filenames or beans interchangeably in commands. * * @param dir * @param beanOrFile * @return ExafsBeansFactory.getBean(new File(beanFile)); * @throws Exception */ public static XMLRichBean getBeanObject(final String dir, final Object beanOrFile) throws Exception { for (int i = 0; i < BeansFactory.getClasses().length; i++) { if (BeansFactory.getClasses()[i].isInstance(beanOrFile)) return (XMLRichBean) beanOrFile; } String path; if(dir != null){ path = dir + beanOrFile; } else { path = beanOrFile.toString(); } if (!path.endsWith(".xml")) path = path + ".xml"; return getBean(new File(path)); } @SuppressWarnings("unused") private static final Logger logger = LoggerFactory.getLogger(XMLHelpers.class); private static URLResolver urlResolver; private static class UrlClassLoaderPair { private URL mappingURL; private ClassLoader cl; public UrlClassLoaderPair(final URL mappingURL, final ClassLoader cl) { this.mappingURL = mappingURL; this.cl = cl; } private boolean equalsHelper(Object o1, Object o2) { if (o1 == o2) return true; if (o1 == null || o2 == null) return false; return o1.equals(o2); } @Override public boolean equals(Object obj) { if (!(obj instanceof UrlClassLoaderPair)) return false; UrlClassLoaderPair other = (UrlClassLoaderPair)obj; if (!equalsHelper(mappingURL, other.mappingURL)) return false; if (!equalsHelper(cl, other.cl)) return false; return true; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((mappingURL == null) ? 0 : mappingURL.hashCode()); result = prime * result + ((cl == null) ? 0 : cl.hashCode()); return result; } } private static Map<UrlClassLoaderPair, XMLContext> xmlContextCache = new HashMap<UrlClassLoaderPair, XMLContext>(); private static XMLContext createXMLContext(final URL mappingURL, final ClassLoader cl) throws MappingException, IOException { XMLContext context; if (mappingURL == null || cl == null) throw new NullPointerException(); UrlClassLoaderPair urlClassLoaderPair = new UrlClassLoaderPair(mappingURL, cl); if (xmlContextCache.containsKey(urlClassLoaderPair)) { context = xmlContextCache.get(urlClassLoaderPair); } else { // Load Mapping. NOTE May need to provide class loader here if class // which are instantiated are on a different class loader to the // one which loaded the mapping class. Mapping mapping = new Mapping(cl); mapping.loadMapping(mappingURL); // initialise and configure XMLContext context = new XMLContext(); context.addMapping(mapping); context.setProperty("org.exolab.castor.indent", "true"); // store in the cache xmlContextCache.put(urlClassLoaderPair, context); } return context; } /** * * @param mappingURL * @param cl * @param schemaUrl * @param filename * @return Object * @throws Exception */ public static Object createFromXML(URL mappingURL, Class<? extends Object> cl, URL schemaUrl, String filename) throws Exception { return XMLHelpers.createFromXML(mappingURL,cl,schemaUrl,filename,true); } public static Object createFromXML(URL mappingURL, Class<? extends Object> cl, URL schemaUrl, String filename, String encoding) throws Exception { return XMLHelpers.createFromXML(mappingURL,cl,schemaUrl,filename,true, encoding); } /** * Creates an object from an inputsource. To create an InputSource from a String use the form: * String xml = "<?xml version="1.0" encoding="UTF-8"?><expt-table-bean><beamsize_horz>beamsize_horz</beamsize_horz></expt-table-bean>" * new InputSource( new StringReader(xml)). * If you use the form new InputSource(xml) you will get the error . org.exolab.castor.xml.MarshalException: no protocol: * * @param mappingURL * @param cl * @param schemaUrl * @param source * @return Object * @throws Exception */ public static Object createFromXML(URL mappingURL, Class<? extends Object> cl, URL schemaUrl, InputSource source) throws Exception { return XMLHelpers.createFromXMLInternal(mappingURL,cl,schemaUrl,source,true); } /** * Unmarshals an object from an XML document. * * @param mappingURL the URL of the Castor mapping file * @param cl class of the object being deserialized * @param schemaUrl URL of the XML schema * @param source the source XML document * @param validate whether the XML document should be validated against the schema * * @return the unmarshalled object * * @throws Exception */ public static Object createFromXML(URL mappingURL, Class<? extends Object> cl, URL schemaUrl, InputSource source, boolean validate) throws Exception { return XMLHelpers.createFromXMLInternal(mappingURL,cl,schemaUrl,source,validate); } /** * * @param mappingURL * @param cl * @param schemaUrl * @param file * @return Object * @throws Exception */ public static Object createFromXML(URL mappingURL, Class<? extends Object> cl, URL schemaUrl, File file) throws Exception { FileReader reader = null; try { reader = new FileReader(file); return XMLHelpers.createFromXMLInternal(mappingURL,cl,schemaUrl,new InputSource(reader),true); } finally { if (reader!=null) reader.close(); } } public static Object createFromXML(URL mappingURL, Class<? extends Object> cl, URL schemaUrl, File file, String encoding) throws Exception { InputStreamReader reader = new InputStreamReader(new FileInputStream(file), encoding); try { return XMLHelpers.createFromXMLInternal(mappingURL,cl,schemaUrl,new InputSource(reader),true); } finally { reader.close(); } } /** * @param mappingURL * @param cl * @param schemaUrl * @param filename * @param validate * @return Object * @throws Exception * @throws XMLHelpersException */ public static Object createFromXML(URL mappingURL, Class<? extends Object> cl, URL schemaUrl, String filename, final boolean validate) throws Exception { return createFromXML(mappingURL, cl, schemaUrl, filename, validate, null); } public static Object createFromXML(URL mappingURL, Class<? extends Object> cl, URL schemaUrl, String filename, final boolean validate, String encoding) throws Exception { InputSource source; // GDA-3377 This fails on windows if filename is similar to // c:/data/file because c: is considered the scheme URI uri = new URI(filename); if (uri.getScheme() != null && (uri.getScheme().equals("http") || uri.getScheme().equals("file"))) source = new InputSource(uri.toURL().openStream()); else { if (encoding == null) source = new InputSource(new FileReader(filename)); else source = new InputSource(new InputStreamReader(new FileInputStream(filename), encoding)); } return XMLHelpers.createFromXMLInternal(mappingURL, cl, schemaUrl, source, validate); } /** * Sets up the bean from the XML. Gives an exception with a useful message if * the XML was invalid. Can be used for validating text editors with xml. * * Beans properties will be nullified before transferring the data from the XML * to the bean. * * The bean being set by this method must have a clear() method. This nullifys the * beans properties. * * @param existingBean * @param mappingUrl * @param schemaUrl * @param xml * @throws Exception */ public static void setFromXML(final Object existingBean, URL mappingUrl, URL schemaUrl, String xml) throws Exception { if (urlResolver!=null) { mappingUrl = urlResolver.resolve(mappingUrl); schemaUrl = urlResolver.resolve(schemaUrl); } InputSource source=null; if (schemaUrl != null) { XMLObjectConfigFileValidator validator = new XMLObjectConfigFileValidator(); source = validator.validateSource(schemaUrl.toString(), xml.toCharArray(), true); if (source == null) { throw new XMLHelpersXMLValidationError(); } } try { final Method clear = existingBean.getClass().getMethod("clear"); clear.invoke(existingBean); } catch (Throwable ignored) { // then don't clear } Unmarshaller unmarshaller = createXMLContext(mappingUrl, existingBean.getClass().getClassLoader()).createUnmarshaller(); unmarshaller.setClass(existingBean.getClass()); unmarshaller.setObject(existingBean); unmarshaller.unmarshal(source); } private static Object createFromXMLInternal(URL mappingUrl, Class<? extends Object> cl, URL schemaUrl, InputSource source, final boolean validate) throws Exception { if (urlResolver!=null) { mappingUrl = urlResolver.resolve(mappingUrl); schemaUrl = urlResolver.resolve(schemaUrl); } if (validate) { if (schemaUrl != null) { XMLObjectConfigFileValidator validator = new XMLObjectConfigFileValidator(); source = validator.validateSource(schemaUrl.toString(), source, true); if (source == null) throw new XMLHelpersXMLValidationError(); } } Object obj = null; if (mappingUrl != null) { Unmarshaller unmarshaller = createXMLContext(mappingUrl, cl.getClassLoader()).createUnmarshaller(); unmarshaller.setClass(cl); obj = unmarshaller.unmarshal(source); } else obj = Unmarshaller.unmarshal(cl, source); if (!obj.getClass().equals(cl)) throw new XMLHelpersException("Class created is incorrect = " + obj.getClass().getName()); return obj; } /** * Writes the object to a file. * @param mappingURL * @param object * @param file * @throws Exception * @throws XMLHelpersException */ public static void writeToXML(URL mappingURL, Object object, File file) throws Exception { final Writer writer = new FileWriter(file); XMLHelpers.writeToXMLInternal(mappingURL, object, writer); } public static void writeToXML(URL mappingURL, Object object, File file, String encoding) throws Exception { final Writer writer = new OutputStreamWriter(new FileOutputStream(file), encoding); XMLHelpers.writeToXMLInternal(mappingURL, object, writer); } /** * Writes the object to a file. * @param mappingURL * @param object * @param filename * @throws Exception * @throws XMLHelpersException */ public static void writeToXML(URL mappingURL, Object object, String filename) throws Exception { writeToXML(mappingURL, object, filename, null); } public static void writeToXML(URL mappingURL, Object object, String filename, String encoding) throws Exception { if (filename.startsWith("file:")) filename = filename.substring(5); Writer writer = null; if (encoding == null) writer = new FileWriter(filename); else writer = new OutputStreamWriter(new FileOutputStream(filename), encoding); XMLHelpers.writeToXMLInternal(mappingURL, object, writer); } /** * Writes the object to a string * @param mappingURL * @param object * @param fileData * @throws Exception * @throws XMLHelpersException */ public static void writeToXML(URL mappingURL, Object object, Writer fileData) throws Exception { XMLHelpers.writeToXMLInternal(mappingURL, object, fileData); } private static void writeToXMLInternal(URL mappingURL, Object object, Writer writer) throws Exception { try { if (urlResolver!=null) mappingURL = urlResolver.resolve(mappingURL); if (mappingURL != null) { XMLContext context = createXMLContext(mappingURL, object.getClass().getClassLoader()); Marshaller marshaller = context.createMarshaller(); marshaller.setWriter(writer); marshaller.marshal(object); } else Marshaller.marshal(object, writer); } finally { if (writer!=null) writer.flush(); if (writer!=null) writer.close(); } } /** * Read a bean from a file and the class to read it into. * * The static fields mappingurl(case insensitive) and schemaurl(case insensitive) should * exist in the beanClass. * * @param beanFile * @param beanClass * @return the bean * @throws Exception */ public static Object readBean(File beanFile, Class<?> beanClass) throws Exception { URL mapping = null; URL schema = null; final Field [] fa = beanClass.getFields(); for (int i = 0; i < fa.length; i++) { if (fa[i].getName().equalsIgnoreCase("mappingurl")) { mapping = (URL)fa[i].get(null); } else if (fa[i].getName().equalsIgnoreCase("schemaurl")) { schema = (URL)fa[i].get(null); } } return createFromXML(mapping, beanClass, schema, beanFile); } /** * @return Returns the urlResolver. */ public static URLResolver getUrlResolver() { return urlResolver; } /** * @param urlResolver The urlResolver to set. */ public static void setUrlResolver(URLResolver urlResolver) { XMLHelpers.urlResolver = urlResolver; } }